package rs.etf.kdp.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.List;

import javax.swing.JTextArea;

import rs.etf.kdp.client.Job;

/**
 * For every job one instance of JobManager. Communicate with workstations.
 * @author Djordje Vukovic
 * @version 1.0
 * @created 20-May-2011 2:30:20 AM
 */
public class JobManager extends Thread {


	private Socket client;
	//private ObjectInputStream inputToClient;
	private ObjectOutputStream outputToClient;
	private ObjectInputStream inputToClient;
	JTextArea status;
	private Job job;
	private Heartbeater hb;
	private AvailableNodes nodesAvailable;
	private CurrentSimulations currentSimulations;
	private InterthreadBuffer buffer;
	ConnectionWithClient clientConnection;
	int msgcnt = 0;

	public JobManager(Job job, JTextArea status, ObjectInputStream input, ObjectOutputStream output, Heartbeater hb){
		buffer = new InterthreadBuffer();
		//client = s;
		this.status = status;
		this.inputToClient = input;
		this.outputToClient = output;
		this.hb = hb;
		this.job = job;
		nodesAvailable = hb.getNodes();
		currentSimulations = hb.getCurrentSimulations();
		//clientConnection = new ConnectionWithClient(input, output, buffer, currentSimulations, this, status);
		//clientConnection.setDaemon(true);
		//clientConnection.start();
	}


	public ObjectInputStream getInputToClient() {
		return inputToClient;
	}


	public void setInputToClient(ObjectInputStream inputToClient) {
		this.inputToClient = inputToClient;
	}


	@Override
	public void run() {
		synchronized(this)
		{
			long jobId = job.getServerID();
			String rec = job.getIdentifier();
			status.append("\n Recieved job with identifier: " + rec);
			// creating simulation
			int optimalNumberOfWS = calculateNumberOfNeededWorkstation(nodesAvailable, ServerGUI.optimalNumberOfComponents);
			List<Node> willSimulateOn = nodesAvailable.getFreeWorkstations(optimalNumberOfWS);
			if(willSimulateOn.isEmpty())
			{
				status.append("\nThere are no workstations available");
				return;
			}
			Log.changedState(jobId, "SCHEDULED");
			Log.partsSentTo(willSimulateOn);
			//mark as used
			for(Node i : willSimulateOn) {
				try {
					i.incrementNumberOfJobs();
					i.insertInterthreadBuffer(jobId, buffer);
				} catch (AssignJobToBusyNode e1) {
					status.append("\nTrying to assign a job to a busy workstation");
					return;
				}
			}
			// Create Simulation Object
			Simulation thisSimulation = new Simulation(job, nodesAvailable, willSimulateOn, this);
			currentSimulations.addActiveSimulation(thisSimulation);
			//create results object
			Result res = new Result(willSimulateOn.size());
			thisSimulation.setResults(res);

			// add simulation object to all nodes
			for(Node i : willSimulateOn)
			{
				i.getSimulationsRunning().add(thisSimulation);
			}
			JobDivider divider = new SimpleJobDivider();
			List<Part> parts;
			try
			{
				parts = divider.divideJob(willSimulateOn.size(), job);
			} catch(Exception e)
			{
				try {
					outputToClient.writeObject("Invalid file sent. Simulation is not started");
				} catch (IOException e1) {
					System.out.println("Unable to send error");
				}
				return;
			}
			ComponentLocator locator = new ComponentLocator(job.getServerID()); //implement component locator
			//add interthread buffer
			for(int i=0; i<parts.size(); i++)
			{
				Part p = parts.get(i);
				Node n = willSimulateOn.get(i);
				List<String> components = p.getComponents();
				for(String comp : components)
				{
					int firstBlanco = comp.indexOf(" ");
					String idChar = comp.substring(0, firstBlanco);
					long id = Long.parseLong(idChar);
					locator.insert(id, n);
				}
				ObjectOutputStream streamToPart = n.getOutput();
				//ObjectInputStream inputFromPart = n.getInput();

				SendPermission permission = n.getPermission();
				permission.requireSend();

				try {
					streamToPart.writeObject("part " + job.getServerID());
					streamToPart.flush();
				} catch (IOException e) {
					status.append("\nJobManager: Unable to send initial message to Node");
					return;
				}

				String answer;
				EventFromWS e = buffer.getMessage();
				answer = e.getMessage();

				//status.append("\nRECEIVED FROM WORKSTATION " + answer);
				if(answer.equals("send_part"))
				{
					try {
						streamToPart.writeObject(p);
						streamToPart.flush();
					} catch (IOException ee) {
						status.append("\nUnable to send part object");
						return;
					}
				}

				permission.releaseSent();


			}
			thisSimulation.setSimulationStatus("RUNNING");
			Log.changedState(jobId, "RUNNING");
			///main loop
			while(true)
			{

				EventFromWS e = buffer.getMessage();
				String msg = e.getMessage();


				if(msg.startsWith("client abort"))
				{
					String[] tok = msg.split(" ");
					long id = Long.parseLong(tok[2]);
					Simulation toDestroy = currentSimulations.getActiveSimulationById(id);
					toDestroy.setSimulationStatus("ABORTED");
					toDestroy.abortThisSimulation();
					currentSimulations.removeActiveSimulation(id);
					Log.changedState(id, "ABORTED");
				} else if (msg.startsWith("workstation transport"))
				{
					System.out.println(msgcnt++);
					//ServerGUI.permission.requireSend();
					String[] tok = msg.split(" ");
					long dstID = Long.parseLong(tok[2]);
					Node n = locator.getWorkstationById(dstID);
					SendPermission permission = n.getPermission();
					permission.requireSend();
					ObjectOutputStream oos = n.getOutput();
					//String cmd = "transport " + tok[3] + " " + (msgcnt-1);
					String cmd = "transport " + tok[3];// + " " + tok[4];
					try {
						oos.writeObject(cmd);
						oos.writeObject(e);
					} catch (IOException e1) {
						System.out.println("IOException while trasnporting event");
					}

					permission.releaseSent();	
				}  else if(msg.startsWith("result"))
				{
					String[] tok = msg.split(" ");
					long id = Long.parseLong(tok[1]);

					Simulation sim = currentSimulations.getActiveSimulationById(id);
					Result result = sim.getResults();

					try {
						outputToClient.writeObject(msg);
						outputToClient.writeObject(result);
					} catch (IOException e1) {
						status.append("\nError while sending results to client");
					}

					currentSimulations.removeActiveSimulation(id);
				}


			}
		}


	}

	private int calculateNumberOfNeededWorkstation(AvailableNodes an, int optimalPerWS)
	{
		int num = 0;
		int numberOfComponents = job.getComponents().getListOfComponents().size();
		int workstations = an.getAvailableWorkstations().size();
		int div;

		num = numberOfComponents / optimalPerWS;
		div = numberOfComponents % optimalPerWS;
		if(div > 0) num++;

		if(num>workstations)
		{
			return workstations;
		}

		if(num>0)return num;
		else return 1;
	}


	public Socket getClient() {
		return client;
	}


	public void setClient(Socket client) {
		this.client = client;
	}


	public ObjectOutputStream getOutputToClient() {
		return outputToClient;
	}


	public void setOutputToClient(ObjectOutputStream outputToClient) {
		this.outputToClient = outputToClient;
	}


	public JTextArea getStatus() {
		return status;
	}


	public void setStatus(JTextArea status) {
		this.status = status;
	}


	public Job getJob() {
		return job;
	}


	public void setJob(Job job) {
		this.job = job;
	}


	public AvailableNodes getNodesAvailable() {
		return nodesAvailable;
	}


	public void setNodesAvailable(AvailableNodes nodesAvailable) {
		this.nodesAvailable = nodesAvailable;
	}


	public CurrentSimulations getCurrentSimulations() {
		return currentSimulations;
	}


	public void setCurrentSimulations(CurrentSimulations currentSimulations) {
		this.currentSimulations = currentSimulations;
	}


	public InterthreadBuffer getBuffer() {
		return buffer;
	}


	public void setBuffer(InterthreadBuffer buffer) {
		this.buffer = buffer;
	}


	public ConnectionWithClient getClientConnection() {
		return clientConnection;
	}


	public void setClientConnection(ConnectionWithClient clientConnection) {
		this.clientConnection = clientConnection;
	}

	public Heartbeater getHb() {
		return hb;
	}


	public void setHb(Heartbeater hb) {
		this.hb = hb;
	}


}